Here’s
an example of when to use a parallel loop. Fabrikam Shipping extends
credit to its commercial accounts. It uses customer credit trends to
identify accounts that might pose a credit risk. Each customer account
includes a history of past balance-due amounts. Fabrikam has noticed
that customers who don’t pay their bills often have histories of
steadily increasing balances over a period of several months before
they default.
To identify at-risk
accounts, Fabrikam uses statistical trend analysis to calculate a
projected credit balance for each account. If the analysis predicts
that a customer account will exceed its credit limit within three
months, the account is flagged for manual review by one of Fabrikam’s
credit analysts.
In the application, a
top-level loop iterates over customers in the account repository. The
body of the loop fits a trend line to the balance history, extrapolates
the projected balance, compares it to the credit limit, and assigns the
warning flag if necessary.
An important aspect
of this application is that each customer’s credit status can be
independently calculated. The credit status of one customer doesn’t
depend on the credit status of any other customer. Because the
operations are independent, making the credit analysis application run
faster is simply a matter of replacing a sequential foreach loop with a parallel loop.
The complete source code for this example is online at http://parallelpatterns.codeplex.com.
1. Sequential Credit Review Example
Here’s the sequential version of the credit analysis operation.
static void UpdatePredictionsSequential(
AccountRepository accounts)
{
foreach (Account account in accounts.AllAccounts)
{
Trend trend = SampleUtilities.Fit(account.Balance);
double prediction = trend.Predict(
account.Balance.Length + NumberOfMonths);
account.SeqPrediction = prediction;
account.SeqWarning = prediction < account.Overdraft;
}
}
The UpdatePredictionsSequential method processes each account from the application’s account repository. The Fit
method is a utility function that uses the statistical least squares
method to create a trend line from an array of numbers. The Fit method is a pure function. This means that it doesn’t modify any state.
The prediction is a
three-month projection based on the trend. If a prediction is more
negative than the overdraft limit (credit balances are negative numbers
in the accounting system), the account is flagged for review.
2. Credit Review Example Using Parallel.For Each
The parallel version of the credit scoring analysis is very similar to the sequential version.
static void UpdatePredictionsParallel(AccountRepository accounts)
{
Parallel.ForEach(accounts.AllAccounts, account =>
{
Trend trend = SampleUtilities.Fit(account.Balance);
double prediction = trend.Predict(
account.Balance.Length + NumberOfMonths);
account.ParPrediction = prediction;
account.ParWarning = prediction < account.Overdraft;
});
}
The UpdatePredictionsParallel method is identical to the Up-datePredictionsSequential method, except that the Parallel.ForEach method replaces the foreach operator.
3. Credit Review Example with PLINQ
You can also use PLINQ to express a parallel loop. Here’s an example.
static void UpdatePredictionsPlinq(AccountRepository accounts)
{
accounts.AllAccounts
.AsParallel()
.ForAll(account =>
{
Trend trend = SampleUtilities.Fit(account.Balance);
double prediction = trend.Predict(
account.Balance.Length + NumberOfMonths);
account.PlinqPrediction = prediction;
account.PlinqWarning = prediction < account.Overdraft;
});
}
Using PLINQ is almost exactly like using LINQ-to-Objects. PLINQ provides a ParallelEnumerable class that defines extension methods for various types in a manner very similar to LINQ’s Enumerable class. One of the methods of ParallelEnumerable is the AsParallel extension method.
The AsParallel extension method allows you to convert a sequential collection of type IEnumerable<T> into a ParallelQuery<T> object. Applying AsParallel to the accounts.AllAccounts collection returns an object of type ParallelQuery<AccountRecord>.
PLINQ’s ParallelEnumerable class has close to 200 extension methods that provide parallel queries for ParallelQuery<T> objects. In addition to parallel implementations of LINQ methods, such as Select and Where, PLINQ provides a ForAll extension method that invokes a delegate method in parallel for every element.
In the PLINQ prediction example, the argument to ForAll
is a lambda expression that performs the credit analysis for a
specified account. The body is the same as in the sequential version.
4. Performance Comparison
Running the credit review example on a quad-core computer shows that the Parallel.ForEach
and PLINQ versions run slightly less than four times as fast as the
sequential version. Timing numbers vary; you may want to run the online
samples on your own computer.